package com.foreveross.mofang.datapersistence.infra.utils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.Date;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.http.HttpResponse;
import org.slf4j.Logger;

import com.foreveross.mofang.datapersistence.infra.Constant;
import com.foreveross.mofang.datapersistence.infra.utils.HttpClientUtil.Reqeust;

/**
 * 文件工具类，主要操作FastDFS操作
 * 
 * @author fb
 */
public class MoFangFileUitl {

	private static Logger logger = MoFangLogger.FILEOPERATION.getLogger();
	
	private static int BUFFER_SIZE = 1024;
	
	private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2 + 2);
	
	private static ReentrantLock lock = new ReentrantLock();
	
	private static volatile BufferedWriter appendWriter = null;
	
	private static volatile File disasterRecovery = null;

	/**
	 * 以http的方式从fastDFS的web服务器文件下载
	 * 
	 * @param url	    文件下载地址
	 * @param dirPath  文件下载存放目录
	 * @return
	 * @throws Exception
	 */
	public static File fastDFSFileDown(String url, String dirPath) throws Exception {
		File localFile = createFile(dirPath);
		byte buffer[] = new byte[BUFFER_SIZE];
		HttpClientUtil client = null;
		InputStream networkFile = null;
		FileOutputStream out = null;
		int size = 0;
		
		logger.info("=====================================================================");
		logger.info("文件下载路径 为：{}", url);
		
		try {
			out = new FileOutputStream(localFile);
			client = HttpClientUtil.getInstance();
			HttpResponse  response = client.getHttpResponse(url, null, null, null, null, Reqeust.GET);
			networkFile = response.getEntity().getContent();
			
			while ((size = networkFile.read(buffer)) > 0) {
				 out.write(buffer, 0, size);
			}
			
			logger.info("文件下载完成, 存放目录为:[{}]", localFile);
		} catch (Exception e) {
			logger.error("文件下载出异常：", e);
			closeOutStream(out);    //此处流不关，空文件无法删除只能finally中多重复关闭一次如果异常
			localFile.delete();
			throw e;
		} finally {
			MoFangFileUitl.closeOutStream(out);
			MoFangFileUitl.closeInStream(networkFile);
			
			if (client != null) {
				client.httpClientClose();
			}
		}
		
		return localFile;
	}
	
	public static void delete(File fileOrDir) {
		
		if (fileOrDir.isFile()) {
			logger.info("准备删除文件[{}]", fileOrDir);
			
			fileOrDir.delete();
		} else {
			try {
				FileUtils.deleteDirectory(fileOrDir);
			} catch (IOException e) {
				logger.info("删除目录[{}]失败", fileOrDir);
			}
		}
		
		logger.info("删除文件或目录[{}]成功", fileOrDir);
	}
	/**
	 * zip文件解压，默认解压到当前文件的相同目录
	 * 
	 * @param zipFile
	 *            解压文件
	 * @return 解压后的目录
	 * @throws IOException
	 */
	public static String unZip(File zipFile) throws Exception{
		String extractDir = zipFile.getPath().substring(0, zipFile.getPath().lastIndexOf(".")).concat(File.separator);
		ZipFile zf = null;
		ZipInputStream zin = null;
		OutputStream out = null;
		ZipEntry ze;
		String dirPath = null;
		InputStream input = null;
		byte buffer[] = new byte[BUFFER_SIZE];
		int readSize = 0;
		
		logger.info("文件{}开始解压，解压路径为[{}]", zipFile.getName(), extractDir);
		
		createDir(extractDir);
		
		try {
			zf = new ZipFile(zipFile);
			zin = new ZipInputStream(new FileInputStream(zipFile));

			while ((ze = zin.getNextEntry()) != null) {
				// 如果是目录，则在解压目录创建相应文件夹
				if (ze.isDirectory()) {
					dirPath = null;
					dirPath = extractDir + ze.getName() + File.separator;
					createDir(dirPath);
				} else {
					if (ze.getSize() == 0) {
						continue;
					}
					
					// 每次将上一次的输出流关闭，以免造成内存溢出
					closeOutStream(out);
					out = new FileOutputStream(new File(extractDir + ze.getName()));
					input = zf.getInputStream(ze);

					while ((readSize = input.read(buffer)) > 0) {
						out.write(buffer, 0, readSize);
					}
					
					closeInStream(input);
				}
			}
			
			logger.info("文件解压成功!");
		} catch (Exception e) {
			logger.error("解压文件出错：", e);
			throw e;
		} finally {
			MoFangFileUitl.closeOutStream(out);	
			MoFangFileUitl.closeInStream(zin);
			MoFangFileUitl.closeInStream(input);
			
			if (zf != null) {
				zf.close();
			}
		}

		return extractDir;
	}
	
	/**
	 * 文件zip压缩方法
	 * 
	 * @param file 
	 */
	public static void zip(File file) {
		byte buffer[] = new byte[BUFFER_SIZE];
		InputStream input = null;
		ZipOutputStream out = null;
		String zipFile = file.getPath().substring(0, file.getPath().lastIndexOf(".")).concat(".zip");
		int size = 0;
		
		logger.info("准备压缩数据文件[{}]为zip格式", file);
		
		try {
			input = new FileInputStream(file);
			out = new ZipOutputStream(new FileOutputStream(zipFile));
			out.putNextEntry(new ZipEntry(file.getName()));
			
			while ((size = input.read(buffer)) > 0) {
				out.write(buffer, 0, size);
			}
			
			out.finish();
			
			logger.info("文件[{}]压缩成功, 新文件为[{}]!", file, zipFile);
			// 此处关闭是因为只有关闭后才能删除文件
			MoFangFileUitl.closeInStream(input);
			MoFangFileUitl.delete(file);
		} catch (Exception e) {
			logger.error("文件数据压缩zip时出错, 出错文件为：[{}]", file, e);
		} finally {
			MoFangFileUitl.closeOutStream(out);	
			MoFangFileUitl.closeInStream(input);
		}
	}
	
	/**
	 * 目录中文件内容读取方法
	 * 
	 * @param directory
	 *            读取目录
	 * @param callback
	 *            数据回调方法 （目录中每个文件特定分隔段数据）
	 * @throws Exception
	 */
	public static void readDirectoryFile(String directory, IFileReadCallback<?> callback) throws Exception {
		InputStream input = null;
		
		for (File file : new File(directory).listFiles()) {
			if (file.isDirectory()) {
				logger.info("正进入文件夹：{}", file.getName());
				readDirectoryFile(file, callback);
				continue;
			} 
			
			input = new FileInputStream(file);
			logger.info("正准备处理文件：{}", file.getName());
			callback.setFileInputStream(input);
			logger.info("文件{}处理完成", file.getName());
			closeInStream(input);
		}
	}
	
	/**
	 * 异常进行处理操作
	 * 
	 * @param directory
	 * @param callback
	 * @throws Exception
	 */
	public static void asyncReadDirectoryFile(final String directory, final IFileReadCallback<?> callback) throws Exception {
		executor.execute(new Runnable() {
			
			public void run() {
				try {
					MoFangFileUitl.readDirectoryFile(directory, callback);
					MoFangFileUitl.delete(new File(directory));
				} catch (Exception e) {
					// 此处只能try住，如果要在主进程中捕获，则使用UncaughtExceptionHandler
					logger.error("异步目录读取出错:", e);
				}
			}
		});
		
	}
	
	/**
	 * 文件MD5值校验
	 * 
	 * @param file
	 *            待校验文件
	 * @param md5
	 *            目标md5值
	 * @return
	 * @throws Exception
	 */
	public static boolean fileMD5Check(File file, String md5) throws Exception {
		FileInputStream fileInputStream = null;
		DigestInputStream digestInputStream = null;
		String fileMd5 = null;
		
		try {
			MessageDigest md = MessageDigest.getInstance("md5");
			fileInputStream = new FileInputStream(file);
			digestInputStream = new DigestInputStream(fileInputStream, md);
			byte[] buffer = new byte[BUFFER_SIZE];
			while (digestInputStream.read(buffer) > 0);
			fileMd5 = Hex.encodeHexString(md.digest());
			
			logger.info("待比较Md5值为：{},文件[{}]的MD5值为：{}", new Object[]{md5, file.getName(), fileMd5});
			
			return fileMd5.equalsIgnoreCase(md5);
		} catch (IllegalArgumentException e) {
			logger.error("文件[{}] MD5生成出错：", file.getName(), e);
			throw e;
		} finally {
			closeInStream(digestInputStream);
			closeInStream(fileInputStream);
		}
	}
	
	public static void closeOutStream(OutputStream out) {
		if (out != null) {
			try {
				out.flush();
				out.close();
			} catch (IOException e) {
				logger.error("输出流关闭异常:", e);
			}
		}
	}
	
	public static void closeInStream(InputStream in) {
		if (in != null) {
			try {
				in.close();
			} catch (IOException e) {
				logger.error("输入流关闭异常:", e);
			}
		}
	}
	
	/**
	 * 创建指定的目录，如果存在则直接反回
	 * 
	 * @param dirPath 目录 
	 */
	public static void createDir(String dirPath) {
		File dir = new File(dirPath);
		
		if (!dir.exists()) {
			dir.mkdirs();
		}
	}
	
	/**
	 * 追加数据到指定的文件中， 按指定文件大小自动切换文件
	 * 
	 * @param dataline
	 */
	public static void appendFile(String dataline) {
		Properties sysPro = PropertiesUtil.getProperties(Constant.PROPERTIES_SYSTEM, CacheUtils.CacheInstance.SYSTEM);
		String dir = sysPro.getProperty("system.dataPersistence.error.dataSavePath") + File.separator ;
		int fileMaxSize = Integer.parseInt(sysPro.getProperty("system.dataPersistence.error.fileSize"));
		fileMaxSize *= 1024;
		String fileUrl = dir + DateFormatUtils.format(new Date(), "yyyy-MM-ddHH-mm-ss") + ".txt";
		File file = null;
		long fileSize = 0;
		lock.lock();
		
		try {
			if (appendWriter == null) {
				file = new File(fileUrl);
				appendWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true), Constant.FILE_ENCODING));
				disasterRecovery = file;
			}
			
			if (dataline != null) {
				appendWriter.write(dataline);
				appendWriter.write(Constant.FILEDATE_SEPARATOR);
				appendWriter.newLine();
	        }
			
			fileSize = file == null ? disasterRecovery.length() : file.length();
		} catch (Exception e) {
			logger.error("出错数据持化到文件异常：", e);
		} finally {
			try {
				appendWriter.flush();
			} catch (IOException e) {
				logger.error("文件流刷新异常: ", e);
			}
			// 文件大小检查
			if (fileSize > fileMaxSize) {
				IOUtils.closeQuietly(appendWriter);
				zip(disasterRecovery);
				appendWriter = null;
				disasterRecovery = null;
			}
			
			lock.unlock();
		}
	}
	
	private static File createFile(String dirPath) {
		String fileName = UUID.randomUUID().toString() + ".zip";
		// 到时这路径从properties或是集群配制中获取
		String path = dirPath + File.separator;
		createDir(path);

		return new File(path + fileName);
	}
	
	private static void readDirectoryFile(File directory, IFileReadCallback<?> callback) throws Exception {
		InputStream input = null;
		
		for (File file : directory.listFiles()) {
			if (file.isDirectory()) {
				logger.info("正进入文件夹：{}", file.getName());
				readDirectoryFile(file, callback);
				continue;
			} 
			
			input = new FileInputStream(file);
			logger.info("正准备处理文件：{}", file.getName());
			callback.setFileInputStream(input);
			logger.info("文件{}处理完成", file.getName());
			closeInStream(input);
		}
	}
	
}
